OpenClaw 记忆系统深度解析
记忆系统是 OpenClaw 实现持续学习和上下文保持的核心机制。本文深入讲解 MEMORY.md、memory 文件、记忆检索、记忆维护等核心概念,帮助你构建有"记忆"的 AI 助理。
概述
OpenClaw 的记忆系统分为三层:
┌─────────────────────────────────────────────────────┐
│ 记忆系统架构 │
├─────────────────────────────────────────────────────┤
│ 短期记忆 │ 会话历史(自动维护) │
│ 中期记忆 │ memory/YYYY-MM-DD.md(每日日志) │
│ 长期记忆 │ MEMORY.md( curated 经验) │
└─────────────────────────────────────────────────────┘记忆系统的作用:
- 📝 记录重要事件和决策
- 🧠 保持跨会话的上下文
- 📚 积累领域知识和经验
- 🔄 支持持续学习和改进
- 🔍 快速检索相关信息
一、记忆文件结构
1.1 文件位置
~/.openclaw/workspace/
├── MEMORY.md # 长期记忆( curated)
└── memory/ # 中期记忆目录
├── YYYY-MM-DD.md # 每日日志
├── YYYY-MM-DD 主题.md # 专题日志
└── heartbeat-state.json # 心跳状态1.2 MEMORY.md 格式
MEMORY.md 是长期记忆的载体,存储经过筛选的重要信息:
markdown
# MEMORY.md - 长期记忆
## 项目信息
- 项目名称:AiTimes 网站
- 技术栈:VitePress, Node.js
- 部署:Gitee Pages
## 用户偏好
- 回复风格:简洁,不啰嗦
- 执行风格:严格命令,不耍滑头
- 消息格式:不加"✅ xxx"小尾巴
- 定时任务:成功不汇报,失败才汇报
## 重要决策
- 2026-03-15: 选择 VitePress 作为静态网站生成器
- 2026-03-18: 确定每日发布 5-10 篇文章的目标
- 2026-03-19: 建立发布流程规范
## 经验教训
- 发布流程必须添加超时控制
- 大文件操作要分批次处理
- 网络请求必须设置超时
## 待办事项
- [ ] 优化发布流程性能
- [ ] 添加错误通知机制
- [ ] 实现断点续传功能
## 关键凭证(加密存储)
- Feishu 凭证:~/.openclaw/credentials/encrypted/feishu-secret.json.gpg
- Gateway Token: ~/.openclaw/credentials/encrypted/gateway-secret.json.gpg
---
最后更新:2026-03-19 19:001.3 每日日志格式
markdown
# 2026-03-19
## 📅 日期信息
- 日期:2026-03-19
- 星期:星期四
- 天气:晴,15-25°C
## 🎯 今日目标
- [x] 完成文章发布流程优化
- [x] 修复 Feishu 集成问题
- [ ] 编写技能开发文档
## 📝 工作日志
### 上午 (08:00-12:00)
- 08:00 生成每日早报(百度热搜 + 天气)
- 09:30 修复发布流程超时问题
- 11:00 用户反馈:文章发布卡住
### 下午 (13:00-18:00)
- 13:30 分析发布流程卡住原因
- 14:00 实施改进方案(超时 + 重试)
- 16:00 测试新流程,成功发布 2 篇文章
### 晚上 (19:00-23:00)
- 19:00 生成每日晚报
- 20:00 整理今日经验到 MEMORY.md
## 💡 经验总结
1. 发布流程必须添加超时控制(30-120 秒)
2. 关键步骤需要重试机制(最多 3 次)
3. 失败时立即通知用户
## 🐛 遇到的问题
- 发布流程在 git push 阶段卡住 30 分钟
- 原因:网络超时设置过长,缺少错误处理
- 解决:添加超时控制和重试机制
## 📊 统计数据
- 处理任务:15 个
- 创建文件:8 个
- 发送消息:12 条
- Token 使用:约 50K
---
生成时间:2026-03-19 23:00二、记忆检索
2.1 检索 API
OpenClaw 提供 memory_search 和 memory_get 两个核心 API:
javascript
// 语义搜索记忆
async function searchMemory(query, options = {}) {
return await memory_search({
query,
maxResults: options.maxResults || 5,
minScore: options.minScore || 0.5
})
}
// 获取记忆片段
async function getMemorySnippet(path, options = {}) {
return await memory_get({
path,
from: options.from || 1,
lines: options.lines || 10
})
}2.2 检索时机
必须检索记忆的场景:
- 回答关于历史的问题
- 执行与之前相关的任务
- 用户提到"之前"、"上次"等词
- 需要上下文连续性的对话
检索示例:
javascript
async function answerWithMemory(question) {
// 1. 搜索相关记忆
const memories = await memory_search({
query: question,
maxResults: 5
})
// 2. 构建上下文
const context = memories.snippets.map(s =>
`来自 ${s.path}:${s.lines}: ${s.content}`
).join('\n\n')
// 3. 使用记忆回答问题
const answer = await sessions_spawn({
task: `基于以下记忆回答问题:${question}
相关记忆:
${context}
如果记忆中没有相关信息,请说明。`,
mode: 'run'
})
return answer.output
}2.3 实战案例 1:智能上下文恢复
javascript
class ContextRestorer {
constructor() {
this.cache = new Map()
}
// 恢复项目上下文
async restoreProjectContext(projectName) {
// 1. 检查缓存
if (this.cache.has(projectName)) {
return this.cache.get(projectName)
}
// 2. 搜索记忆
const memories = await memory_search({
query: `项目 ${projectName} 配置 技术栈 决策`,
maxResults: 10
})
// 3. 提取关键信息
const context = {
name: projectName,
techStack: this.extractTechStack(memories),
decisions: this.extractDecisions(memories),
issues: this.extractIssues(memories),
todos: this.extractTodos(memories)
}
// 4. 缓存
this.cache.set(projectName, context)
return context
}
extractTechStack(memories) {
const techKeywords = ['React', 'Vue', 'Node.js', 'Python', 'VitePress']
const techStack = new Set()
for (const snippet of memories.snippets) {
for (const tech of techKeywords) {
if (snippet.content.includes(tech)) {
techStack.add(tech)
}
}
}
return Array.from(techStack)
}
extractDecisions(memories) {
return memories.snippets
.filter(s => s.content.includes('决策') || s.content.includes('决定'))
.map(s => s.content)
}
extractIssues(memories) {
return memories.snippets
.filter(s => s.content.includes('问题') || s.content.includes('bug'))
.map(s => s.content)
}
extractTodos(memories) {
return memories.snippets
.filter(s => s.content.includes('[ ]') || s.content.includes('待办'))
.map(s => s.content)
}
}
// 使用
const restorer = new ContextRestorer()
const context = await restorer.restoreProjectContext('AiTimes')
console.log('项目上下文:', context)三、记忆写入
3.1 写入时机
应该写入记忆的内容:
- ✅ 重要决策和原因
- ✅ 学到的经验教训
- ✅ 用户偏好和习惯
- ✅ 项目关键信息
- ✅ 问题和解决方案
- ✅ 待办事项和进度
不应该写入记忆的内容:
- ❌ 临时性信息
- ❌ 敏感数据(密码、密钥)
- ❌ 过于琐碎的细节
- ❌ 可轻易重新获取的信息
3.2 写入每日日志
javascript
async function logDailyEvent(event) {
const today = new Date().toISOString().split('T')[0]
const logPath = `/home/pao/.openclaw/workspace/memory/${today}.md`
// 读取或创建日志文件
let content = ''
try {
content = await read({ path: logPath })
} catch (e) {
// 文件不存在,创建模板
content = `# ${today}\n\n## 📝 工作日志\n\n`
}
// 添加事件
const eventEntry = `### ${event.time}
${event.description}
`
// 追加到日志
await edit({
path: logPath,
oldText: content,
newText: content + eventEntry
})
}
// 使用
await logDailyEvent({
time: '14:30',
description: '修复发布流程超时问题,添加重试机制'
})3.3 写入长期记忆
javascript
async function addToLongTermMemory(entry) {
const memoryPath = '/home/pao/.openclaw/workspace/MEMORY.md'
// 读取现有记忆
let content = await read({ path: memoryPath })
// 根据类型添加到对应章节
switch (entry.type) {
case 'decision':
content = appendToSection(content, '重要决策', `- ${entry.date}: ${entry.content}`)
break
case 'lesson':
content = appendToSection(content, '经验教训', `- ${entry.content}`)
break
case 'preference':
content = appendToSection(content, '用户偏好', `- ${entry.content}`)
break
case 'todo':
content = appendToSection(content, '待办事项', `- [ ] ${entry.content}`)
break
default:
content = appendToSection(content, '其他', `- ${entry.content}`)
}
// 更新最后更新时间
content = content.replace(
/最后更新:.*/,
`最后更新:${new Date().toLocaleString('zh-CN')}`
)
// 写回文件
await write({ path: memoryPath, content })
}
function appendToSection(content, section, newItem) {
const sectionRegex = new RegExp(`(## ${section}\\n)(.*?)(\\n##|$)`, 's')
const match = content.match(sectionRegex)
if (match) {
// 添加到现有章节
return content.replace(
sectionRegex,
`$1$2${newItem}\n$3`
)
} else {
// 创建新章节
return content + `\n## ${section}\n${newItem}\n`
}
}
// 使用
await addToLongTermMemory({
type: 'lesson',
date: '2026-03-19',
content: '发布流程必须添加超时控制(30-120 秒)'
})3.4 实战案例 2:自动经验提取
javascript
class ExperienceExtractor {
constructor() {
this.patterns = {
lesson: [
/必须 (添加 | 设置 | 配置)/,
/不要 (忘记 | 忽略)/,
/应该 (先 | 再)/,
/最佳实践是/,
/经验教训/
],
decision: [
/决定/,
/选择.*作为/,
/确定.*方案/,
/采用.*策略/
],
issue: [
/问题.*是/,
/原因是/,
/解决方案/,
/修复.*问题/
]
}
}
// 从对话中提取经验
async extractFromConversation(messages) {
const extractions = []
for (const message of messages) {
// 检查是否是 AI 的消息
if (message.role !== 'assistant') continue
const content = message.content
// 提取经验教训
for (const pattern of this.patterns.lesson) {
if (pattern.test(content)) {
extractions.push({
type: 'lesson',
content: this.extractSentence(content, pattern),
timestamp: message.timestamp
})
}
}
// 提取决策
for (const pattern of this.patterns.decision) {
if (pattern.test(content)) {
extractions.push({
type: 'decision',
content: this.extractSentence(content, pattern),
timestamp: message.timestamp
})
}
}
}
return extractions
}
extractSentence(content, pattern) {
const sentences = content.split(/[.!?。!?]/)
for (const sentence of sentences) {
if (pattern.test(sentence)) {
return sentence.trim()
}
}
return content.slice(0, 100)
}
// 定期整理记忆
async consolidateMemories() {
// 1. 读取最近的每日日志
const recentLogs = await this.getRecentLogs(7)
// 2. 提取重要内容
const extractions = []
for (const log of recentLogs) {
const extracted = await this.extractFromLog(log)
extractions.push(...extracted)
}
// 3. 去重和优先级排序
const unique = this.deduplicate(extractions)
const prioritized = this.prioritize(unique)
// 4. 添加到长期记忆
for (const item of prioritized.slice(0, 10)) {
await addToLongTermMemory(item)
}
return {
processed: recentLogs.length,
extracted: extractions.length,
added: prioritized.slice(0, 10).length
}
}
async getRecentLogs(days) {
const logs = []
const today = new Date()
for (let i = 0; i < days; i++) {
const date = new Date(today)
date.setDate(date.getDate() - i)
const dateStr = date.toISOString().split('T')[0]
try {
const content = await read({
path: `/home/pao/.openclaw/workspace/memory/${dateStr}.md`
})
logs.push({ date: dateStr, content })
} catch (e) {
// 日志不存在,跳过
}
}
return logs
}
async extractFromLog(log) {
// 使用 AI 提取
const result = await sessions_spawn({
task: `从以下日志中提取重要经验、决策和问题:
${log.content}
返回 JSON 格式:
[
{"type": "lesson|decision|issue", "content": "..."},
...
]`,
mode: 'run'
})
return JSON.parse(result.output)
}
deduplicate(items) {
const seen = new Set()
return items.filter(item => {
const key = `${item.type}:${item.content}`
if (seen.has(key)) return false
seen.add(key)
return true
})
}
prioritize(items) {
// 按类型和关键词优先级排序
const priority = {
lesson: 3,
decision: 2,
issue: 1
}
return items.sort((a, b) => {
const scoreA = priority[a.type] || 0
const scoreB = priority[b.type] || 0
return scoreB - scoreA
})
}
}
// 使用
const extractor = new ExperienceExtractor()
// 每天执行一次记忆整理
async function dailyConsolidation() {
const result = await extractor.consolidateMemories()
console.log('记忆整理完成:', result)
}四、记忆维护
4.1 定期回顾
javascript
class MemoryMaintainer {
constructor() {
this.reviewIntervals = {
daily: 1, // 每天
weekly: 7, // 每周
monthly: 30 // 每月
}
}
// 每日回顾
async dailyReview() {
const today = new Date().toISOString().split('T')[0]
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0]
// 读取昨天的日志
try {
const yesterdayLog = await read({
path: `/home/pao/.openclaw/workspace/memory/${yesterday}.md`
})
// 检查是否有待办未完成
const pendingTodos = yesterdayLog.match(/\[ \] .*/g) || []
if (pendingTodos.length > 0) {
console.log('有待办事项未完成:', pendingTodos)
// 可以迁移到今天的日志
}
} catch (e) {
// 日志不存在
}
}
// 每周回顾
async weeklyReview() {
const weekLogs = await this.getWeekLogs()
// 生成周报
const weeklyReport = await sessions_spawn({
task: `基于以下一周的日志生成周报:
${weekLogs.map(l => `## ${l.date}\n${l.content}`).join('\n\n')}
包括:
- 完成的主要工作
- 遇到的问题和解决
- 学到的经验
- 下周计划`,
mode: 'run'
})
// 保存周报
await write({
path: `/home/pao/.openclaw/workspace/memory/weekly-${this.getWeekNumber()}.md`,
content: weeklyReport.output
})
}
// 每月清理
async monthlyCleanup() {
const memoryPath = '/home/pao/.openclaw/workspace/MEMORY.md'
let content = await read({ path: memoryPath })
// 移除过时的待办(已完成的)
content = content.replace(/^- \[x\] .*\n/gm, '')
// 移除过时的临时信息
content = content.replace(/^- 临时:.*\n/gm, '')
// 写回
await write({ path: memoryPath, content })
console.log('月度记忆清理完成')
}
async getWeekLogs() {
const logs = []
const today = new Date()
for (let i = 0; i < 7; i++) {
const date = new Date(today)
date.setDate(date.getDate() - i)
const dateStr = date.toISOString().split('T')[0]
try {
const content = await read({
path: `/home/pao/.openclaw/workspace/memory/${dateStr}.md`
})
logs.push({ date: dateStr, content })
} catch (e) {
// 跳过
}
}
return logs
}
getWeekNumber() {
const date = new Date()
const startOfYear = new Date(date.getFullYear(), 0, 1)
const days = Math.floor((date - startOfYear) / 86400000)
return Math.ceil((days + startOfYear.getDay() + 1) / 7)
}
}
// 使用
const maintainer = new MemoryMaintainer()
// 定时执行
// 每日回顾:每天 08:00
// 每周回顾:每周一 09:00
// 每月清理:每月 1 号 10:004.2 记忆压缩
当日志文件过大时,需要压缩:
javascript
async function compressOldLogs(daysToKeep = 30) {
const memoryDir = '/home/pao/.openclaw/workspace/memory'
const files = await exec({
command: `ls -1 ${memoryDir}/*.md | sort`
})
const cutoffDate = new Date(Date.now() - daysToKeep * 86400000)
for (const file of files.stdout.split('\n').filter(f => f)) {
const match = file.match(/(\d{4}-\d{2}-\d{2})\.md$/)
if (!match) continue
const fileDate = new Date(match[1])
if (fileDate < cutoffDate) {
// 压缩旧日志
const content = await read({ path: file })
// 提取关键信息
const summary = await sessions_spawn({
task: `总结以下日志的关键信息(300 字以内):
${content}`,
mode: 'run'
})
// 添加到 MEMORY.md
await addToLongTermMemory({
type: 'archive',
date: match[1],
content: summary.output
})
// 删除旧日志(或移动到 archive 目录)
await exec({
command: `mv ${file} ${memoryDir}/archive/`
})
}
}
}4.3 实战案例 3:记忆健康检查
javascript
class MemoryHealthChecker {
async check() {
const report = {
status: 'healthy',
issues: [],
recommendations: []
}
// 1. 检查 MEMORY.md 存在性
try {
await read({ path: '/home/pao/.openclaw/workspace/MEMORY.md' })
} catch (e) {
report.issues.push('MEMORY.md 不存在')
report.status = 'warning'
}
// 2. 检查 memory 目录
const memoryFiles = await exec({
command: 'ls -1 /home/pao/.openclaw/workspace/memory/*.md 2>/dev/null | wc -l'
})
const fileCount = parseInt(memoryFiles.stdout.trim())
if (fileCount === 0) {
report.issues.push('memory 目录为空')
report.status = 'warning'
}
// 3. 检查最近日志
const today = new Date().toISOString().split('T')[0]
try {
await read({ path: `/home/pao/.openclaw/workspace/memory/${today}.md` })
} catch (e) {
report.recommendations.push('创建今日日志')
}
// 4. 检查文件大小
const largeFiles = await exec({
command: 'find /home/pao/.openclaw/workspace/memory -name "*.md" -size +1M 2>/dev/null'
})
if (largeFiles.stdout.trim()) {
report.issues.push('发现过大文件,建议压缩')
report.recommendations.push('运行记忆压缩')
}
// 5. 检查待办事项
const memoryContent = await read({
path: '/home/pao/.openclaw/workspace/MEMORY.md'
}).catch(() => '')
const pendingTodos = (memoryContent.match(/\[ \]/g) || []).length
if (pendingTodos > 20) {
report.recommendations.push(`有待办事项 ${pendingTodos} 个,建议清理`)
}
// 6. 检查最后更新时间
const lastUpdateMatch = memoryContent.match(/最后更新:(.*)/)
if (lastUpdateMatch) {
const lastUpdate = new Date(lastUpdateMatch[1])
const daysSinceUpdate = (Date.now() - lastUpdate) / 86400000
if (daysSinceUpdate > 7) {
report.recommendations.push(`记忆已超过${Math.round(daysSinceUpdate)}天未更新`)
}
}
return report
}
async autoFix(report) {
for (const issue of report.issues) {
if (issue === 'MEMORY.md 不存在') {
await write({
path: '/home/pao/.openclaw/workspace/MEMORY.md',
content: `# MEMORY.md - 长期记忆\n\n## 项目信息\n\n## 用户偏好\n\n## 重要决策\n\n## 经验教训\n\n## 待办事项\n\n---\n最后更新:${new Date().toLocaleString('zh-CN')}\n`
})
}
}
for (const rec of report.recommendations) {
if (rec === '创建今日日志') {
const today = new Date().toISOString().split('T')[0]
await write({
path: `/home/pao/.openclaw/workspace/memory/${today}.md`,
content: `# ${today}\n\n## 📝 工作日志\n\n## 💡 经验总结\n\n## 🐛 遇到的问题\n\n---\n生成时间:${new Date().toLocaleString('zh-CN')}\n`
})
}
}
}
}
// 使用
const checker = new MemoryHealthChecker()
// 定期健康检查
async function memoryHealthCheck() {
const report = await checker.check()
console.log('记忆系统健康检查:')
console.log(`状态:${report.status}`)
console.log(`问题:${report.issues.length}`)
console.log(`建议:${report.recommendations.length}`)
if (report.issues.length > 0) {
await checker.autoFix(report)
}
return report
}五、安全与隐私
5.1 敏感信息处理
javascript
// 不写入记忆的内容
const SENSITIVE_PATTERNS = [
/password[:\s]*[^\s]+/i,
/secret[:\s]*[^\s]+/i,
/token[:\s]*[^\s]+/i,
/api[_-]?key[:\s]*[^\s]+/i,
/\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}/, // 信用卡号
/\b\d{11}\b/ // 手机号
]
function sanitizeForMemory(content) {
let sanitized = content
for (const pattern of SENSITIVE_PATTERNS) {
sanitized = sanitized.replace(pattern, '[REDACTED]')
}
return sanitized
}
// 使用
await logDailyEvent({
time: '10:00',
description: sanitizeForMemory('配置了 API 密钥:sk-abc123...')
// 实际写入:'配置了 API 密钥:[REDACTED]'
})5.2 访问控制
javascript
// 在群聊中不加载 MEMORY.md
async function shouldLoadMemory(context) {
// 主会话(直接聊天)可以加载
if (context.type === 'direct') {
return true
}
// 群聊、Discord 等公共场合不加载
if (context.type === 'group' || context.platform === 'discord') {
return false
}
// 默认不加载
return false
}
// 使用
if (await shouldLoadMemory(currentContext)) {
const memories = await memory_search({ query: userQuestion })
// 使用记忆回答问题
}5.3 加密存储
对于特别敏感的信息,使用加密存储:
bash
# 加密存储凭证
echo "sensitive-data" | gpg --symmetric --cipher-algo AES256 \
-o ~/.openclaw/credentials/encrypted/sensitive.gpg
# 解密读取
gpg --decrypt ~/.openclaw/credentials/encrypted/sensitive.gpg在记忆中只存储路径:
markdown
## 关键凭证
- 数据库密码:~/.openclaw/credentials/encrypted/db-password.gpg六、最佳实践
6.1 记忆写入原则
SMART 原则:
- Specific - 具体明确
- Meaningful - 有意义
- Actionable - 可操作
- Referencable - 可检索
- Timely - 及时更新
示例对比:
markdown
❌ 模糊:今天修了个 bug
✅ 具体:修复发布流程在 git push 阶段超时的问题,添加 60 秒超时和 3 次重试
❌ 无意义:用户说喜欢简洁
✅ 有意义:用户偏好简洁回复,不使用"✅ xxx"小尾巴,成功不汇报
❌ 不可操作:系统要优化
✅ 可操作:发布流程添加超时控制(构建 60s、部署 30s、验证 20s)6.2 检索优化
提高检索准确率:
javascript
// 使用具体关键词
await memory_search({ query: '发布流程 超时 重试' }) // ✅
await memory_search({ query: '问题' }) // ❌ 太模糊
// 组合多个搜索
const results1 = await memory_search({ query: 'Feishu 集成' })
const results2 = await memory_search({ query: '飞书 API' })
const combined = mergeResults(results1, results2)6.3 记忆组织
分类建议:
markdown
# MEMORY.md 结构建议
## 项目信息
- 每个项目一个子章节
## 用户偏好
- 回复风格
- 执行习惯
- 通知偏好
## 重要决策
- 按日期排序
- 包含决策原因
## 经验教训
- 按领域分类(技术、流程、沟通)
## 待办事项
- 区分优先级
- 定期清理已完成
## 关键配置
- 只存路径,不存明文七、总结
核心要点
- 记忆系统是 AI 持续学习的基础
- 区分短期、中期、长期记忆
- 及时写入重要信息
- 定期回顾和清理
- 注意安全和隐私
记忆生命周期
事件发生 → 写入每日日志 → 定期提取 → 存入长期记忆 → 定期回顾 → 压缩归档进阶方向
- 📖 实现更智能的经验提取
- 🔍 优化记忆检索算法
- 📊 可视化记忆网络
- 🤖 自动化记忆维护
🟢🐉 开始构建你的 AI 记忆系统吧!